package org.Yiran.timetale_re.weather.manager;

import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.Yiran.timetale_re.Timetale_re;
import org.Yiran.timetale_re.weather.StormWeatherData;
import org.joml.Vector2f;
import org.joml.Vector3f;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * 风力管理系统
 * <p>
 * 负责计算和管理游戏世界中的风力数据，包括风力强度、风向等参数。
 * 根据高度、生物群系、天气、时间等多种因素综合计算风力，
 * 并考虑方块阻挡对风力的影响。
 * </p>
 *
 * @author Yiran
 * @version 1.0
 */
@Mod.EventBusSubscriber(modid = Timetale_re.MODID)
public class WindManager {
    
    // ================================
    // 数据存储和常量定义
    // ================================
    
    /**
     * 存储各世界风力数据的映射表
     * Key为世界维度，Value为对应的风力数据
     */
    private static final Map<ResourceKey<Level>, WindData> worldWindData = new HashMap<>();
    
    /**
     * 随机数生成器
     */
    private static final Random random = new Random();

    // 风力影响因素权重
    /**
     * 高度影响权重
     */
    private static final float HEIGHT_FACTOR = 0.3f;
    
    /**
     * 生物群系影响权重
     */
    private static final float BIOME_FACTOR = 0.25f;
    
    /**
     * 天气影响权重
     */
    private static final float WEATHER_FACTOR = 0.2f;
    
    /**
     * 时间影响权重
     */
    private static final float TIME_FACTOR = 0.15f;
    
    /**
     * 随机因素权重
     */
    private static final float RANDOM_FACTOR = 0.1f;

    // 基础风力值范围
    /**
     * 最小基础风力值
     */
    private static final float MIN_BASE_WIND = 0.5f;
    
    /**
     * 最大基础风力值
     */
    private static final float MAX_BASE_WIND = 3.0f;

    // 缓存相关
    /**
     * 方块阻挡因子缓存映射表
     */
    private static final Map<BlockPos, Float> blockObstructionCache = new HashMap<>();
    
    /**
     * 缓存大小限制
     */
    private static final int CACHE_SIZE_LIMIT = 1000;
    
    /**
     * 上次清理缓存的游戏时间
     */
    private static long lastCacheClear = 0;

    // ================================
    // 风力数据获取方法
    // ================================
    
    /**
     * 获取指定世界的风力数据
     * <p>
     * 如果指定世界尚未有风力数据，则创建新的风力数据对象并存储
     * </p>
     *
     * @param world 世界对象，不能为空
     * @return 指定世界的风力数据对象
     */
    public static WindData getWindData(Level world) {
        return worldWindData.computeIfAbsent(world.dimension(), key -> new WindData());
    }

    // ================================
    // 风力强度计算方法
    // ================================
    
    /**
     * 计算指定位置的风力强度
     * <p>
     * 综合考虑多种因素计算风力强度：
     * 1. 维度检查（下界无风）
     * 2. 自定义大风天气
     * 3. 高度因素
     * 4. 生物群系因素
     * 5. 天气因素
     * 6. 时间因素
     * 7. 方块阻挡因素
     * </p>
     *
     * @param world 世界对象
     * @param pos   位置坐标
     * @return 风力强度值，最小值为0.1f
     */
    public static float calculateWindStrength(Level world, BlockPos pos) {
        WindData windData = getWindData(world);

        // 检查是否为下界维度，如果是则风力为0
        if (world.dimension() == Level.NETHER) {
            return 0.0f;
        }

        // 检查是否有自定义大风天气
        if (world instanceof ServerLevel serverLevel) {
            StormWeatherData weatherData = StormWeatherData.get(serverLevel);
            if (weatherData.isWindy() && world.getGameTime() < weatherData.getStormEndTime()) {
                // 使用自定义风力强度
                float intensity = weatherData.getWindIntensity();

                // 根据距离地表高度调整风力
                float heightFactor = Math.min(1.0f, Math.max(0.1f, pos.getY() / 256.0f));

                // 添加一些随机扰动
                float randomVariation = 0.8f + (random.nextFloat() * 0.4f); // 0.8-1.2倍变化

                return intensity * heightFactor * randomVariation;
            }
        }

        // 高度因素 (越高风越大)
        float heightFactor = Math.min(1.0f, pos.getY() / 320.0f);

        // 如果在地下，减少风力影响
        if (pos.getY() < 60) {
            heightFactor *= 0f;
        }

        // 生物群系因素
        float biomeFactor = getBiomeWindFactor(world, pos);

        // 天气因素
        float weatherFactor = 1.0f;
        if (world.isRainingAt(pos)) {
            weatherFactor = 1.3f; // 下雨时风力增强
        }
        if (world.isThundering()) {
            weatherFactor = 1.6f; // 雷雨时风力更强
        }

        // 时间因素 (夜晚风力通常较大)
        long dayTime = world.getDayTime() % 24000;
        float timeFactor = 1.0f;
        if (dayTime >= 13000 && dayTime <= 23000) { // 夜晚
            timeFactor = 1.2f;
        }

        // 综合计算
        float baseWind = windData.getBaseWindStrength();
        float calculatedWind = baseWind *
                (1.0f +
                        heightFactor * HEIGHT_FACTOR +
                        biomeFactor * BIOME_FACTOR +
                        (weatherFactor - 1.0f) * WEATHER_FACTOR +
                        (timeFactor - 1.0f) * TIME_FACTOR);

        // 考虑方块阻挡影响
        float blockObstructionFactor = calculateBlockObstructionFactor(world, pos);
        calculatedWind *= blockObstructionFactor;

        return Math.max(0.1f, calculatedWind);
    }

    // ================================
    // 方块阻挡计算方法
    // ================================
    
    /**
     * 计算方块阻挡对风力的影响因子
     * <p>
     * 通过检测指定位置周围方块的密度来计算风力阻挡因子，
     * 并使用缓存机制提高性能
     * </p>
     *
     * @param world 世界对象
     * @param pos   位置坐标
     * @return 风力阻挡因子 (0.0-1.0)，值越小表示阻挡越严重
     */
    public static float calculateBlockObstructionFactor(Level world, BlockPos pos) {
        // 检查缓存
        if (blockObstructionCache.containsKey(pos)) {
            return blockObstructionCache.get(pos);
        }

        // 定期清理缓存
        if (world.getGameTime() - lastCacheClear > 6000) {
            blockObstructionCache.clear();
            lastCacheClear = world.getGameTime();
        }

        // 检查是否在完全封闭的空间中
        int solidNeighbors = 0;
        int totalNeighbors = 0;

        // 检查周围的方块，扩大检测范围
        for (int x = -2; x <= 2; x++) { // 减少检测范围以提高性能
            for (int y = -2; y <= 2; y++) {
                for (int z = -2; z <= 2; z++) {
                    if (x == 0 && y == 0 && z == 0) continue; // 跳过中心位置

                    BlockPos checkPos = pos.offset(x, y, z);
                    var blockState = world.getBlockState(checkPos);
                    totalNeighbors++;

                    // 如果是实体方块，增加计数
                    if (!blockState.isAir() && blockState.isCollisionShapeFullBlock(world, checkPos)) {
                        solidNeighbors++;
                    }
                }
            }
        }

        // 如果周围有很多固体方块，说明在封闭空间中
        float ratio = (float) solidNeighbors / totalNeighbors;

        // 在完全封闭的空间中（如洞穴、房间），风力应该大幅减弱
        float result;
        if (ratio > 0.7) { // 超过70%被方块包围
            result = 0.05f; // 几乎没有风力
        } else if (ratio > 0.5) {
            result = 0.1f; // 很小的风力
        } else if (ratio > 0.3) {
            result = 0.2f; // 较小的风力
        } else if (ratio > 0.1) {
            result = 0.5f; // 中等风力
        } else {
            result = 1.0f; // 开阔地带，正常风力
        }

        // 限制缓存大小
        if (blockObstructionCache.size() < CACHE_SIZE_LIMIT) {
            blockObstructionCache.put(pos, result);
        }

        return result;
    }

    /**
     * 获取方块对风力的阻挡因子
     * <p>
     * 根据方块类型和碰撞形状计算单个方块对风力的阻挡程度
     * </p>
     *
     * @param world 世界对象
     * @param pos   方块位置
     * @return 阻挡因子 (0.0-1.0)，值越小表示阻挡越严重
     */
    private static float getBlockWindObstructionFactor(Level world, BlockPos pos) {
        var blockState = world.getBlockState(pos);

        // 空气和液体不阻挡风
        if (blockState.isAir() || blockState.getFluidState().isSource()) {
            return 0.0f;
        }

        // 树叶等有孔隙的方块阻挡较少
        if (blockState.is(net.minecraft.tags.BlockTags.LEAVES)) {
            return 0.2f; // 减少树叶的阻挡效果
        }

        // 植物类方块（草、花等）
        if (blockState.is(net.minecraft.tags.BlockTags.FLOWERS) ||
                blockState.is(net.minecraft.tags.BlockTags.REPLACEABLE)) {
            return 0.05f;
        }

        // 获取碰撞形状
        var shape = blockState.getCollisionShape(world, pos);

        // 检查形状是否为空
        if (shape.isEmpty()) {
            return 0.0f; // 空形状不阻挡风
        }

        // 计算碰撞体积
        var bounds = shape.bounds();
        double volume = (bounds.maxX - bounds.minX) * (bounds.maxY - bounds.minY) * (bounds.maxZ - bounds.minZ);

        if (volume < 0.3) {
            return 0.1f; // 极小阻挡
        } else if (volume < 0.7) {
            return 0.3f; // 小阻挡
        } else if (volume >= 0.9) {
            return 0.8f; // 大阻挡但不是完全阻挡
        } else {
            return 0.6f; // 中等阻挡
        }
    }

    // ================================
    // 风向计算方法
    // ================================
    
    /**
     * 计算指定位置的风向 (返回水平面上的风向向量)
     * <p>
     * 获取指定位置的风向向量，考虑时间因素对风向的影响
     * </p>
     *
     * @param world 世界对象
     * @param pos   位置坐标
     * @return 风向向量（二维向量，X和Y分量表示水平面上的方向）
     */
    public static Vector2f calculateWindDirection(Level world, BlockPos pos) {
        WindData windData = getWindData(world);
        // 结合时间因素计算风向
        return windData.getWindDirectionWithTimeInfluence(world.getDayTime());
    }

    /**
     * 计算指定位置的风力向量 (包含方向和强度)
     * <p>
     * 综合风力强度和风向计算完整的风力向量
     * </p>
     *
     * @param world 世界对象
     * @param pos   位置坐标
     * @return 风力向量（三维向量，Y分量为0表示水平风）
     */
    public static Vector3f calculateWindVector(Level world, BlockPos pos) {
        float strength = calculateWindStrength(world, pos);
        Vector2f direction = calculateWindDirection(world, pos);
        return new Vector3f(direction.x, 0.0f, direction.y).mul(strength);
    }

    // ================================
    // 生物群系因素计算方法
    // ================================
    
    /**
     * 根据生物群系获取风力因素
     * <p>
     * 根据位置所在的生物群系类型返回对应的风力影响因子
     * </p>
     *
     * @param level 世界对象
     * @param pos   位置坐标
     * @return 风力影响因子，正值表示增强风力，负值表示减弱风力
     */
    private static float getBiomeWindFactor(Level level, BlockPos pos) {
        // 通过Level获取生物群系的资源位置
        ResourceLocation biomeLocation = level.registryAccess()
                .registryOrThrow(Registries.BIOME)
                .getKey(level.getBiome(pos).value());

        if (biomeLocation == null) {
            return 0.0f;
        }

        String biomeName = biomeLocation.getPath().toLowerCase();

        // 根据生物群系名称判断风力因素
        if (biomeName.contains("desert")) {
            return 0.5f;   // 沙漠风力较大
        } else if (biomeName.contains("mountain") || biomeName.contains("hill") || biomeName.contains("peak")) {
            return 0.8f;   // 山地风力很大
        } else if (biomeName.contains("forest") || biomeName.contains("jungle") || biomeName.contains("wood")) {
            return -0.1f;  // 森林风力较小
        } else if (biomeName.contains("swamp") || biomeName.contains("mushroom")) {
            return -0.3f;  // 沼泽风力很小
        } else if (biomeName.contains("ocean") || biomeName.contains("beach")) {
            return 0.6f;   // 海洋风力较大
        } else if (biomeName.contains("plains")) {
            return 0.2f;   // 平原风力适中
        } else if (biomeName.contains("end")) {
            return 0.4f;   // 下界/末地风力适中
        } else if (biomeName.contains("ice") || biomeName.contains("snow") || biomeName.contains("frozen")) {
            return 0.5f;   // 冰原风力较大
        } else {
            return 0.0f;   // 默认无影响
        }
    }

    // ================================
    // 事件处理方法
    // ================================
    
    /**
     * 世界Tick事件，用于更新风力数据
     * <p>
     * 在每个世界tick结束时更新风力数据，包括基础风力强度和风向
     * </p>
     *
     * @param event 世界tick事件对象
     */
    @SubscribeEvent
    public static void onWorldTick(TickEvent.LevelTickEvent event) {
        if (event.phase == TickEvent.Phase.END && !event.level.isClientSide() && event.level instanceof ServerLevel serverWorld) {
            WindData windData = getWindData(serverWorld);

            // 每隔更长时间更新风力值（原来是100，现在改为1200，即1分钟）
            if (serverWorld.getGameTime() % 1200 == 0) {
                windData.updateBaseWindStrength();
            }

            // 更频繁但更小幅度地更新风向（每200tick更新一次）
            if (serverWorld.getGameTime() % 200 == 0) {
                windData.updateWindDirection(serverWorld.getDayTime());
            }

            // 每tick插值更新风向
            windData.interpolateWindDirection();
        }
    }

    // ================================
    // 风力数据内部类
    // ================================
    
    /**
     * 风力数据类
     * <p>
     * 封装单个世界的风力数据，包括基础风力强度、当前风向、目标风向等属性，
     * 以及相关的更新和序列化方法
     * </p>
     */
    public static class WindData {
        /**
         * 基础风力强度
         */
        private float baseWindStrength = 1.0f;
        
        /**
         * 目标风向
         */
        private Vector2f targetWindDirection = new Vector2f(1.0f, 0.0f);
        
        /**
         * 当前风向
         */
        private Vector2f currentWindDirection = new Vector2f(1.0f, 0.0f);
        
        /**
         * 基础风向模式
         */
        private Vector2f baseWindPattern = new Vector2f(1.0f, 0.0f);
        
        /**
         * 上次更新tick
         */
        private long lastUpdateTick = 0;

        /**
         * 构造一个新的风力数据对象
         * <p>
         * 初始化基础风力值、风向和基础风向模式
         * </p>
         */
        public WindData() {
            // 初始化基础风力值
            this.baseWindStrength = MIN_BASE_WIND + random.nextFloat() * (MAX_BASE_WIND - MIN_BASE_WIND);
            // 初始化风向
            updateWindDirection(0);
            this.currentWindDirection = new Vector2f(targetWindDirection.x, targetWindDirection.y);
            // 初始化基础风向模式
            float baseAngle = random.nextFloat() * 360.0f;
            double radians = Math.toRadians(baseAngle);
            this.baseWindPattern = new Vector2f((float) Math.cos(radians), (float) Math.sin(radians));
        }

        /**
         * 获取基础风力强度
         *
         * @return 基础风力强度值
         */
        public float getBaseWindStrength() {
            return baseWindStrength;
        }

        /**
         * 获取风向 (标准化的2D向量)
         * <p>
         * 使用插值使风向变化更加平滑
         * </p>
         *
         * @return 当前风向向量
         */
        public Vector2f getWindDirection() {
            return currentWindDirection;
        }

        /**
         * 根据时间获取受时间影响的风向
         * <p>
         * 结合当前时间和风向计算受时间影响的风向向量
         * </p>
         *
         * @param dayTime 当前世界时间
         * @return 受时间影响的风向向量
         */
        public Vector2f getWindDirectionWithTimeInfluence(long dayTime) {
            // 计算当前时间在一天中的位置 (0-1)
            float timeProgress = (dayTime % 24000) / 24000.0f;

            // 基于时间计算周期性风向偏移
            // 使用正弦和余弦函数创建平滑的周期性变化
            float timeOffsetAngle = timeProgress * 360.0f; // 一天一圈
            float timeInfluenceX = (float) (0.3 * Math.cos(Math.toRadians(timeOffsetAngle)));
            float timeInfluenceY = (float) (0.3 * Math.sin(Math.toRadians(timeOffsetAngle)));

            // 将时间影响与当前风向结合
            float resultX = currentWindDirection.x + timeInfluenceX;
            float resultY = currentWindDirection.y + timeInfluenceY;

            // 标准化向量
            float length = (float) Math.sqrt(resultX * resultX + resultY * resultY);
            if (length > 0) {
                resultX /= length;
                resultY /= length;
            }

            return new Vector2f(resultX, resultY);
        }

        /**
         * 更新基础风力强度
         * <p>
         * 使用更平滑的变化方式更新基础风力强度
         * </p>
         */
        public void updateBaseWindStrength() {
            // 更小的波动范围 ±10%（原来是±20%）
            float change = (random.nextFloat() - 0.5f) * 0.2f;
            this.baseWindStrength = Math.max(MIN_BASE_WIND, Math.min(MAX_BASE_WIND, baseWindStrength * (1.0f + change)));
        }

        /**
         * 更新目标风向
         * <p>
         * 结合时间因素和随机因素更新目标风向
         * </p>
         *
         * @param dayTime 当前世界时间
         */
        public void updateWindDirection(long dayTime) {
            // 计算当前时间在一天中的位置 (0-1)
            float timeProgress = (dayTime % 24000) / 24000.0f;

            // 基于基础风向模式和时间计算目标风向
            float baseAngle = (float) Math.toDegrees(Math.atan2(baseWindPattern.y, baseWindPattern.x));

            // 添加时间相关的周期性变化
            float timeInfluence = (float) (30.0 * Math.sin(2 * Math.PI * timeProgress)); // ±30度的周期性变化

            // 添加一些随机变化
            float randomVariation = (random.nextFloat() - 0.5f) * 40.0f; // ±20度的随机变化

            float targetAngle = baseAngle + timeInfluence + randomVariation;

            double radians = Math.toRadians(targetAngle);
            float x = (float) Math.cos(radians);
            float y = (float) Math.sin(radians);
            this.targetWindDirection = new Vector2f(x, y);

            // 偶尔更新基础风向模式（较少频率）
            if (random.nextFloat() < 0.1f) { // 10%概率
                float newBaseAngle = baseAngle + (random.nextFloat() - 0.5f) * 90.0f; // ±45度变化
                double newRadians = Math.toRadians(newBaseAngle);
                this.baseWindPattern = new Vector2f((float) Math.cos(newRadians), (float) Math.sin(newRadians));
            }
        }

        /**
         * 每tick更新当前风向，使其逐渐接近目标风向
         * <p>
         * 通过插值算法使风向变化更加平滑自然
         * </p>
         */
        public void interpolateWindDirection() {
            // 计算当前风向到目标风向的角度差
            float currentAngle = (float) Math.toDegrees(Math.atan2(currentWindDirection.y, currentWindDirection.x));
            float targetAngle = (float) Math.toDegrees(Math.atan2(targetWindDirection.y, targetWindDirection.x));

            // 处理角度跨越0度的问题
            float angleDiff = targetAngle - currentAngle;
            if (angleDiff > 180) angleDiff -= 360;
            if (angleDiff < -180) angleDiff += 360;

            // 每次只移动角度差的1/20，使变化更加平滑
            float newAngle = currentAngle + angleDiff * 0.05f;

            double radians = Math.toRadians(newAngle);
            float x = (float) Math.cos(radians);
            float y = (float) Math.sin(radians);
            this.currentWindDirection = new Vector2f(x, y);
        }

        /**
         * 序列化到NBT
         * <p>
         * 将风力数据序列化为NBT标签，用于保存和传输
         * </p>
         *
         * @return 包含风力数据的NBT标签
         */
        public CompoundTag serializeNBT() {
            CompoundTag nbt = new CompoundTag();
            nbt.putFloat("BaseWindStrength", baseWindStrength);
            nbt.putLong("LastUpdateTick", lastUpdateTick);
            nbt.putFloat("CurrentWindDirectionX", currentWindDirection.x);
            nbt.putFloat("CurrentWindDirectionY", currentWindDirection.y);
            nbt.putFloat("TargetWindDirectionX", targetWindDirection.x);
            nbt.putFloat("TargetWindDirectionY", targetWindDirection.y);
            nbt.putFloat("BaseWindPatternX", baseWindPattern.x);
            nbt.putFloat("BaseWindPatternY", baseWindPattern.y);
            return nbt;
        }

        /**
         * 从NBT反序列化
         * <p>
         * 从NBT标签中恢复风力数据
         * </p>
         *
         * @param nbt 包含风力数据的NBT标签
         */
        public void deserializeNBT(CompoundTag nbt) {
            this.baseWindStrength = nbt.getFloat("BaseWindStrength");
            this.lastUpdateTick = nbt.getLong("LastUpdateTick");
            float currentX = nbt.getFloat("CurrentWindDirectionX");
            float currentY = nbt.getFloat("CurrentWindDirectionY");
            this.currentWindDirection = new Vector2f(currentX, currentY);

            float targetX = nbt.getFloat("TargetWindDirectionX");
            float targetY = nbt.getFloat("TargetWindDirectionY");
            this.targetWindDirection = new Vector2f(targetX, targetY);

            float baseX = nbt.getFloat("BaseWindPatternX");
            float baseY = nbt.getFloat("BaseWindPatternY");
            this.baseWindPattern = new Vector2f(baseX, baseY);
        }
    }
}